package simpledb;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
LockManager
 锁管理器类
 用于实现对页面进行锁分配，判断有无发生死锁
 */
public class LockManager {
    //每个page上相对应的锁 用LockState表示
    private Map<PageId, List<LockState>> pageLocks;
    //表示事务在请求某个page时被阻塞的map  事务id->pageId
    private Map<TransactionId,PageId> waitingInfo;

    public LockManager() {
        this.pageLocks = new ConcurrentHashMap<>();
        this.waitingInfo = new ConcurrentHashMap<>();
    }

    public Map<PageId, List<LockState>> getPageLocks() {
        return pageLocks;
    }

    public void setPageLocks(Map<PageId, List<LockState>> pageLocks) {
        this.pageLocks = pageLocks;
    }

    public Map<TransactionId, PageId> getWaitingInfo() {
        return waitingInfo;
    }

    public void setWaitingInfo(Map<TransactionId, PageId> waitingInfo) {
        this.waitingInfo = waitingInfo;
    }

    /**
     * 给page赋予s锁
     * 1 当该page上没有锁时 直接给当前page上锁
     * 2  当该page上全部都s锁时  若当前tid没有上s锁，则上s锁。否则表示当前tid上过了s锁返回true
     * 3   当该page上已有x锁  判断该x锁是否是当前tid的，若是 可以上s锁，否则被阻塞。
     * 使用synchronized同步
     * @param tid
     * @param pid
     * @return
     */
    public synchronized boolean grantSLock(TransactionId tid,PageId pid)
    {
        List<LockState> locks = pageLocks.get(pid);
        if(locks==null||locks.size()==0)
        {
           return lock(pid,tid,Permissions.READ_ONLY);
        }
        else if(locks.size()==1)
        {
           if(locks.get(0).getPerm()==Permissions.READ_WRITE)
           {
               return locks.get(0).getTid().equals(tid)?lock(pid,tid,Permissions.READ_ONLY):wait(tid,pid);
           }
           else
           {
               if(locks.get(0).getTid().equals(tid))
               {
                   return true;
               }
               else
                   return lock(pid,tid,Permissions.READ_ONLY);
           }
        }
        else
        {
            for (LockState ls:locks)
            {
                if(ls.getPerm()==Permissions.READ_WRITE)
                {
                    return locks.get(0).getTid().equals(tid)?true:wait(tid,pid);
                }

            }
            return lock(pid,tid,Permissions.READ_ONLY);
        }
    }

    /**
     * 给某一page上X锁
     * 1 当page无锁时  上x锁
     * 2 当page只有一个锁时  若是写锁且不是当前tid的，阻塞。若是读锁 判断是否为当前tid，如果是则可以加x锁，否则阻塞。
     * 3 当page有2个锁时  只有2个锁是当前tid的s锁和x锁返回true  其余情况阻塞
     * @param tid
     * @param pid
     * @return
     */
    public synchronized boolean grantXLock(TransactionId tid,PageId pid)
    {
        List<LockState> lockStates = pageLocks.get(pid);
        if(lockStates==null||lockStates.size()==0)
        {
            return lock(pid,tid,Permissions.READ_WRITE);
        }
        else if(lockStates.size()==1)
        {
            if(lockStates.get(0).getPerm()==Permissions.READ_ONLY)
            {
                return lockStates.get(0).getTid().equals(tid)?lock(pid,tid,Permissions.READ_WRITE):wait(tid,pid);
            }
            else
            {
                return lockStates.get(0).getTid().equals(tid);
            }
        }
        else if(lockStates.size()==2)
        {
            for (LockState ls:lockStates)
            {
                if(ls.getPerm()==Permissions.READ_WRITE)
                {
                    return ls.getTid()==tid;
                }
            }
            return wait(tid,pid);
        }
        else
        {
             return wait(tid,pid);
        }
    }

    /**
     *
     * @param pid 上锁的page
     * @param tid  想要加锁tid
     * @param perm 加锁的类型  s/x锁
     * @return
     *
     */
    private synchronized boolean lock(PageId pid,TransactionId tid,Permissions perm)
    {
        LockState lockState = new LockState(perm, tid);
        List<LockState> lockStates = pageLocks.get(pid);
        if(lockStates==null)
        {
            lockStates = new ArrayList<LockState>();
        }
        lockStates.add(lockState);
        pageLocks.put(pid,lockStates);
        return true;
    }

    //阻塞当前请求page的行为，代码上体现为将其加入等待队列waitingInfo
    private synchronized boolean wait(TransactionId tid,PageId pid)
    {
        waitingInfo.put(tid,pid);
        return false;
    }

    /**
     *
     * @param tid
     * @param pid
     * @return
     * 判断某个事务tid对page pid的请求是否发生死锁  该代码通过判断是否有资源的循环请求来判断死锁
     * 假设三个事务t1 t2 t3 分别持有对p1 p2 p3三个page的锁
     * 此时t1请求p3 t3请求p2 t2请求p1
     * 各自持有资源并且请求其他的人持有的资源，形成环，有死锁发生。
     * 以 t1请求p3是否发生死锁为例 tid=t1 pid=p3
     */
    public boolean isDeadLock(TransactionId tid,PageId pid)
    {
        //取得page上持有的锁  也就是p3被哪些事务所持有
        List<LockState> holders = pageLocks.get(pid);
        //表明当前page空闲，没有死锁发生
        if(holders==null||holders.size()==0)
        {
            return false;
        }
        //取得事务tid所拥有的pages  t1所占有的page
        List<PageId> pages = getAllPagesByTid(tid);
        for(LockState ls:holders)
        {
            TransactionId lsTid = ls.getTid();
            //假如tid持有的page中不含有pid表示的page，则进行死锁判断
            if(!lsTid.equals(tid))
            {
                //lsTid t3 持有被请求的page上的事务
                //tid t1   对page请求的事务t1
                //pages p1  t1持有的page资源
                //isWaitingResouce 此时表明t1要请求t3所持有的资源p3,判断是否t3需要请求t1所占有的资源
                boolean res = isWaitingResouce(lsTid,tid,pages);
            }
        }
        return false;
    }

    /**
     *
     * @param tid
     * @param rootTx
     * @param pages
     * @return
     *
     */
    private boolean isWaitingResouce(TransactionId tid, TransactionId rootTx,List<PageId> pages)
    {
        //取得tid t3所请求的资源
        PageId pageId = waitingInfo.get(tid);
        //如果t3没有请求资源，表示资源不会循环请求，没有死锁发生
        if(pageId==null)
        {
            return false;
        }
        //判断t3是否直接请求了t1所占有的资源，如果是则表明 t1请求t3持有的资源同时t3也请求t1所占有的资源 ，有死锁发生
        for(PageId pid:pages)
        {
            if(pid.equals(pageId))
            {
                return true;
            }
        }
        boolean res = false;
        //取得t3请求page p2上，持有p2的事务
        List<LockState> lockStates = pageLocks.get(pageId);
        //判断是否有间接资源请求
        for (LockState lockState:lockStates)
        {
            TransactionId lockStateTid = lockState.getTid();
            if(!lockStateTid.equals(tid))
            {
               //判断持有p2的事务t2是否对t1持有资源的请求，使用递归判断这个资源请求是否形成环
               res = isWaitingResouce(lockStateTid,rootTx,pages);
               if(res)
               {
                   return true;
               }
            }
        }
        return false;
    }

    //通过tid获得一个事务所持有的所有page
    public List<PageId> getAllPagesByTid(TransactionId tid)
    {
        List<PageId> pids = new ArrayList<>();
        for (Map.Entry entry:pageLocks.entrySet())
        {
            List<LockState> lockStates = (List<LockState>) entry.getValue();
            PageId pageId = (PageId) entry.getKey();
            for (LockState lockState : lockStates)
            {
                if(lockState.getTid().equals(tid))
                {
                    pids.add(pageId);
                    break;
                }
            }
        }
        return pids;
    }

    //释放一个页面上的所有锁
    public boolean releaseLocksByPid(PageId pid)
    {
        List<LockState> lockStates = pageLocks.get(pid);
        if(lockStates==null||lockStates.size()==0)
           return true;
        pageLocks.remove(pid);
        return true;
    }

    //释放指定事务对指定页面的锁
    public boolean releasePage(TransactionId tid,PageId pid)
    {
        List<LockState> lockStates = pageLocks.get(pid);
        if(lockStates==null||lockStates.size()==0)
            return false;
        LockState lockState = new LockState(Permissions.READ_ONLY,tid);
        if(lockStates.contains(lockState))
        {
            lockStates.remove(lockState);
        }
        lockState = new LockState(Permissions.READ_WRITE,tid);
        if(lockStates.contains(lockState))
        {
            lockStates.remove(lockState);
        }
        return false;

    }





}
